package com.zlx.store.order.service.Impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zlx.store.common.exception.GoodsStockEmptyException;


import com.zlx.store.common.util.StringTool;
import com.zlx.store.domain.entity.*;
import com.zlx.store.domain.entity.vo.*;

import com.zlx.store.order.dao.GoodsDao;
import com.zlx.store.order.dao.OrderDao;
import com.zlx.store.order.dao.OrderRelateGoodsDao;
import com.zlx.store.order.dao.StoreRelateGoodsTabDao;
import com.zlx.store.order.service.IOrderService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;


@Service
public class OrderServiceImpl implements IOrderService {
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private OrderRelateGoodsDao orderRelateGoodsDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StoreRelateGoodsTabDao storeRelateGoodsTabDao;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * VO需要两个东西，一是所有订单，二是与订单关联的所有商品数据
     * 取出的顺序却是反的，先用storeID取出订单商品关联表
     * 再用订单商品关联表的订单id来找出所属的order，然后拼成orderVO发给前端（顺带存一份在redis上，TTL设置为半小时）
     * order在redis中的存储依照权限是分开的，为了避免大key问题，
     * 另外的考虑是是否需要将大key存入java作为缓存
     * 需要一个管理员使用的总order表 OrderAdmin
     * 一个经理使用的店铺order表，OrderStore+StoreId
     * 一张已付款的order用户表OrderUser+userId
     *
     * @param storeId
     * @param userId
     * @return 代码逻辑：
     * 先判断进这些订单在redis中是否存在，如果存在，直接返回redis中的值，如果不存在，
     * 从order和orderRelateGoods对应的表中分别取出符合条件的数值，组成list，并将两个list拼成OrderVO，
     * 返回前端,其中，为了避免类型转换异常，将redis中取出的对象先进行判断，随后再类型转换
     * 这么操作是减少与数据库的通信次数，这样只需两次通信就能将数据取出，而且避免了两表联查的风险,
     * 加锁是为了避免不同的管理员查询空时发生缓存击穿的情况
     * 为了避免缓存穿透，这里采取缓存空对象方法（订单为空，缓存一个不为空的提示订单进去）
     */
    @Override

    public List<OrderVO> find(Long storeId, Long userId) {
        RLock lock = redissonClient.getLock("OrderLock");
        Object o = new Object();
        try {
            lock.lock();
            //查询缓存
            if (StringTool.isNull(storeId, userId)) {
                o = redisTemplate.opsForValue().get("OrderAdmin");
            }
            if (StringTool.isNull(storeId)) {
                o = redisTemplate.opsForValue().get("OrderUser" + userId);
            }
            if (StringTool.isNull(userId)) {
                o = redisTemplate.opsForValue().get("OrderStore" + storeId);
            }
            //为空，开始查询
            if (StringTool.isNull(o)) {
                List<OrderRelateGoods> orderRelateGoodsList = orderRelateGoodsDao.selectList(
                        new LambdaQueryWrapper<OrderRelateGoods>().eq(!StringTool.isNull(storeId), OrderRelateGoods::getStoreId, storeId)
                );
                List<Order> orderList = orderDao.selectList(
                        new LambdaQueryWrapper<Order>().eq(!StringTool.isNull(storeId), Order::getOrderUserId, userId)
                );
                List<OrderVO> orderVOList = null;
//                数据库查询空,缓存不为空的空对象
                if (StringTool.isNull(orderRelateGoodsList, orderList)) {
                    OrderVO orderVO = new OrderVO();
                    orderVO.setOrderAddress("无订单");
                    orderVOList.add(orderVO);
                    if (StringTool.isNull(storeId, userId)) {
                        redisTemplate.opsForValue().set("OrderAdmin", orderVOList, 1000 * 60 * 30);
                    }
                    if (StringTool.isNull(storeId)) {
                        redisTemplate.opsForValue().set("OrderUser" + userId, orderVOList, 1000 * 60 * 30);
                    }
                    if (StringTool.isNull(userId)) {
                        redisTemplate.opsForValue().set("OrderStore" + storeId, orderVOList, 1000 * 60 * 30);
                    }
                    return null;
                }
                orderVOList = orderList.stream()
                        .map(order -> {
                            return new OrderVO().build(
                                    orderRelateGoodsList.stream()
                                            .filter(orderRelateGoods -> orderRelateGoods.getOrderId().equals(order.getOrderId()))
                                            .collect(Collectors.toList()),
                                    order
                            );
                        })
                        .collect(Collectors.toList());
                //vo查询完毕，按照条件存储到redis
                if (StringTool.isNull(storeId, userId)) {
                    redisTemplate.opsForValue().set("OrderAdmin", orderVOList, 1000 * 60 * 30);
                }
                if (StringTool.isNull(storeId)) {
                    redisTemplate.opsForValue().set("OrderUser" + userId, orderVOList, 1000 * 60 * 30);
                }
                if (StringTool.isNull(userId)) {
                    redisTemplate.opsForValue().set("OrderStore" + storeId, orderVOList, 1000 * 60 * 30);
                }
                return orderVOList;
            }
        } finally {
            lock.unlock();
        }
        return (List<OrderVO>) o;
    }

    /**
     * 这是产生订单的过程，首先是创造order对象，然后将商品订单关联的对象拼好，
     * 存入redis，key为PayOrder+userId
     * 返回后端，并放入付款队列
     * 返回订单操作成功,返回订单对象
     *
     * @param shoppingCartVO
     * @param tokenVO
     * @param coupon
     * @return
     */
    @Override
    public Integer insert(ShoppingCartVO shoppingCartVO, TokenVO tokenVO, Coupon coupon, HttpServletResponse response) {
        List<GoodsVO> goodsVOList = shoppingCartVO.getGoodsVOList();
        List<GoodsVO> collect = goodsVOList.stream().filter(goodsVO -> {
            Integer goodsCount = storeRelateGoodsTabDao.selectById(goodsVO.getGoodsId()).getGoodsCount();
            if (goodsVO.getGoodsCount() > goodsCount) {
                throw new GoodsStockEmptyException("无库存");
            }
            goodsDao.selectById(goodsVO.getGoodsId());
            //装配成vo
            return true;
        }).collect(Collectors.toList());
        OrderVO orderVO = new OrderVO();
        orderVO.setOrderId(IdUtil.getSnowflakeNextId());
        orderVO.setGoodsVOList(collect);
        orderVO.setOrderCreateTime(new Date());
        final AtomicReference<Double>[] price = new AtomicReference[]{null};
        final Double[] amount = {null};
        collect.forEach(
                goodsVO -> {
                    price[0].set(goodsVO.getGoodsCount() * goodsVO.getGoodsPrice());
                    amount[0] = amount[0] + price[0].get();
                }
        );
        orderVO.setOrderAmount(amount[0]);
        //使用优惠券
        if (Objects.nonNull(coupon)) {
            Double v = coupon.getCouponDiscount() * amount[0];
            orderVO.setOrderAmount(v);
        }
        orderVO.setOrderState(1);
        List<OrderVO> orderVOList = null;
        orderVOList.add(orderVO);
        redisTemplate.opsForValue().set("PayOrder" + tokenVO.getUserId(), orderVOList, 30, TimeUnit.MINUTES);
        rabbitTemplate.convertAndSend("orderPayExchange"
                , "orderPay", orderVO);
        return null;
    }
}
